上一篇我們成功在 Next.js 安裝 ApolloClient 了,今天我們繼續使用剛安裝的 ApolloClient 從 WordPress 抓取文章資料,在首頁顯示文章列表!
首先打開 WordPress 後台的 GraphiQL IDE,在左邊 Explorer 裡面有各種可 query 的欄位,其中 posts 就是文章列表。展開它並勾選你想使用的欄位,如果不確定欄位意義的話,可以展開右邊 Docs,裡面可以找到每個欄位的簡短說明文字,或是可以將所有想了解的欄位都打勾,直接按執行按鈕,看回傳值是什麼,這是比較快速的方式。
我決定在我首頁文章列表,使用圖中所示的欄位,並且要依照日期由新到舊降序排序,以及只抓取前十篇,於是 GraphiQL 幫我生成的 query string 如下:
query MyQuery {
posts(where: { orderby: { field: DATE, order: DESC } }, first: 10) {
edges {
node {
databaseId
title
uri
date
excerpt
featuredImage {
node {
sourceUrl
altText
}
}
}
}
}
}
讓我們把這段 query 複製進 Next.js 程式碼裡,我建立了一個 graphql 資料夾,用來存放所有 GraphQL query string 和資料型態轉換的邏輯,並在裡面建立了 allPostsQuery.js(完整路徑是 /graphql/allPostsQuery.js),內容如下:
import { gql } from '@apollo/client'
import discardPTag from '../utils/discardPTag'
export const ALL_POSTS_QUERY = gql`
query allPosts($first: Int!) {
posts(where: { orderby: { field: DATE, order: DESC } }, first: $first) {
edges {
node {
databaseId
title
uri
date
excerpt
featuredImage {
node {
sourceUrl
altText
}
}
}
}
}
}
`
export const allPostsQueryVars = {
first: 10,
}
export const transformAllPostsData = (data) => {
return (
data?.posts?.edges
?.map((edge) => edge?.node)
?.map((post) => ({
id: post?.databaseId || '',
title: post?.title || '',
uri: post?.uri || '',
date: post?.date || '',
excerpt: discardPTag(post?.excerpt) || '',
featuredImage: {
sourceUrl: post?.featuredImage?.node?.sourceUrl || '',
altText: post?.featuredImage?.node?.altText || '',
},
})) || []
)
}
裡面放了從 GraphiQL 複製過來的 query string,並且我額外寫了 transformAllPostsData function,用來在等等將 query 回來的資料轉成用起來舒服的資料格式,把串接介面切乾淨。
其中值得注意的是,excerpt 欄位我用來當作文章簡介,這在編輯文章時有欄位可以輸入,若沒有輸入的話,WordPress 也會自動生成,給文章一個摘要,但是這個欄位在 WPGraphQL query 回來時,是會多出前綴後綴 p tag 的,像是 <p>Real Content</p>
,於是我在 transformAllPostsData 裡面多用了另一個 discardPTag function 來移除多餘 p tag,實作寫在 /utils/discardPTag.js 裡頭:
/**
* Discard starting and trailing <p> from a string
* "<p>slice</p>" -> "slice"
* @param {string} source
* @returns string
*/
export default function discardPTag(source) {
return source?.slice(3, -4)
}
接著在首頁的 /pages/index.js 裡,參照 Next.js 官方 with-apollo 範例寫法,在最下面新增 getStaticProps function 來在 server side 產生 ApolloClient 並 query GraphQL API,並在 component 內部使用 useQuery 來使用 query 回來的資料,並用剛剛的 transformAllPostsData 轉換資料,存成 allPosts 陣列,並在 render 時用 array.map 產生文章列表,修改完的完整 index.js 長這樣:
import { useMemo } from 'react'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import { useQuery } from '@apollo/client'
import styles from '../styles/Home.module.css'
import { initializeApollo, addApolloState } from '../lib/apolloClient'
import { allPostsQueryVars, ALL_POSTS_QUERY, transformAllPostsData } from '../graphql/allPostsQuery'
export default function Home() {
const { data } = useQuery(ALL_POSTS_QUERY, {
variables: allPostsQueryVars,
})
const allPosts = useMemo(() => transformAllPostsData(data), [data]) || []
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>就。很。Pro。blog</h1>
<div className={styles.grid}>
{allPosts?.map((post) => (
<Link key={post.id} href={post.uri} passHref>
<a className={styles.card}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</a>
</Link>
))}
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
export async function getStaticProps() {
const apolloClient = initializeApollo()
await apolloClient.query({
query: ALL_POSTS_QUERY,
variables: allPostsQueryVars,
})
return addApolloState(apolloClient, {
props: {},
revalidate: 1,
})
}
最後為了讓樣式稍微能看,限制文章 title 和簡介的行數,我修改了 /styles/Home.module.css,在大約 100 行處的 .card h2 和 .card p 都加了 line-clamp 的樣式,修改完長這樣:
/* ... */
/* At about line 100 */
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
/* ... */
最後執行 yarn dev
,打開瀏覽器進到 http://localhost:3000/ ,你應該就會看到如下畫面,首頁成功顯示了基本文章列表!恭喜你!
這篇文章的相關改動,可以參照這支 commit
下一篇我們會繼續來優化首頁的樣式,我們會安裝 Tailwindcss 這套最近非常熱門的 CSS 框架,來簡單切版!
目前成功讀取數據了,但排版感覺有點複雜,先睡個覺,明天再戰。
先決定不排版了,看看下一篇有沒有靈感。